#include <cassert>
#include <algorithm>

#include "metrics/Builder.hpp"
#include "metrics/ClientMetric.hpp"
#include "metrics/Histogram.hpp"

namespace metrics
{
    Histogram::Histogram(const BucketBoundaries &buckets) : _bucket_boundaries(buckets), _bucket_counts(buckets.size() + 1), _sum(0)
    {
        assert(std::is_sorted(std::begin(_bucket_boundaries), std::end(_bucket_boundaries)));
    }

    Histogram::~Histogram()
    {
    }

    void Histogram::observe(double value)
    {
        const auto bucket_index = static_cast<std::size_t>(std::distance(_bucket_boundaries.begin(), std::find_if(
                                                                                                         std::begin(_bucket_boundaries),
                                                                                                         std::end(_bucket_boundaries),
                                                                                                         [value](const double boundary)
                                                                                                         { return boundary >= value; })));
        std::lock_guard<std::mutex> lock(_mutex);
        _sum += value;
        _bucket_counts[bucket_index]++;
    }

    void Histogram::observe(const std::vector<double> &bucket_increments, const double _sumof_values)
    {
        if (bucket_increments.size() != _bucket_counts.size())
        {
            throw std::length_error("The size of bucket_increments was not equal to the number of buckets in the histogram.");
        }

        std::lock_guard<std::mutex> lock(_mutex);
        _sum += _sumof_values;
        for (std::size_t i{0}; i < _bucket_counts.size(); ++i)
        {
            _bucket_counts[i] += bucket_increments[i];
        }
    }

    ClientMetric Histogram::collect() const
    {
        std::lock_guard<std::mutex> lock(_mutex);

        auto metric = ClientMetric{};

        auto cumulative_count = 0ULL;
        metric.histogram.buckets.reserve(_bucket_counts.size());
        for (std::size_t i{0}; i < _bucket_counts.size(); ++i)
        {
            cumulative_count += _bucket_counts[i];
            auto bucket = ClientMetric::Bucket{};
            bucket.cumulative_count = cumulative_count;
            bucket.upper_bound = (i == _bucket_boundaries.size() ? std::numeric_limits<double>::infinity() : _bucket_boundaries[i]);
            metric.histogram.buckets.push_back(std::move(bucket));
        }
        metric.histogram.sample_count = cumulative_count;
        metric.histogram.sample_sum = _sum;

        return metric;
    }

    Builder<Histogram> BuildHistogram() { return {}; }

} // namespace metrics
